home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Pascal Super Library
/
Pascal Super Library (CW International)(1997).bin
/
DELPHI32
/
DIALOGS
/
BRWSFLDR
/
BROWSEFO.PAS
< prev
next >
Wrap
Pascal/Delphi Source File
|
1996-09-28
|
21KB
|
492 lines
//--------------------------------------------------------------------------------------------------------//
// TBrowseFolder Component
// Written by Todd Fast
// Copyright (C) 1996 by Pencilneck Software. All rights reserved.
// Version 1.0, lego 9-25-96
//
//
// Description:
//
// Native Delphi component that encapsulates the SHBrowseForFolder interface, which allows
// Win32 users to select a directory using the standard Explorer-like treeview dialog.
//
//
// Contact:
//
// tfast@eden.com
// pencilneck@hotmail.com
//
//
// Distribution:
//
// This component is freeware. As such, Pencilneck Software gives no warranty to its accuracy,
// fitness for any particular use, effects of use, or reliability. This component may not be
// distributed as a part of another component package without Pencilneck Software's written
// consent. It may be freely distributed, although it must be distributed with all original
// files in their original format intact. If you use this component in your software, please
// include an acknowledgment that portions are copyrighted by Pencilneck Software. Please
// contact the author, Todd Fast, at one of the above addresses with questions, comments,
// bug-reports or any updates you make to the component.
//
//
// Properties:
//
// CallbackParam:
// App-specific value passed to the callback function from the browse dialog.
// Flags:
// Set of flags for determining what the browse dialog will allow the user to choose.
// Enforces these restrictions by enabling or disabling the OK button when a user
// chooses a particular type of file item.
// Folder:
// The top-level folder displayed in the browse dialog. foDesktop is the default
// and is what users are used to seeing in Explorer.
// ShowFullPath:
// Enables or disables a custom feature that shows the selected path in the status area
// of the browse dialog. Must have the bfStatusText flag set.
// Title:
// The title text shown in the browse dialog.
// DisplayName:
// Read-only. The display name returned from the dialog in the BROWSEINFO structure;
// ImageIndex:
// Read-only. The selected item's image index in the system image list. Returned in
// the BROWSEINFO structure.
// Directory:
// Read-only. The path of the chosen directory.
// BrowseDialogShowing:
// Read-only. Set to TRUE when the browse dialog is already showing.
//
//
// Methods:
//
// constructor Create(AOwner: TComponent);
// Standard constructor for TComponent.
//
// function Execute: Boolean;
// Shows the browse dialog and allows the user to choose a directory. Returns true if
// the user chose the OK button, FALSE if he or she chose the Cancel button.
//
// procedure SetStatusText(const Hwnd: HWND; const StatusText: String);
// Hwnd:
// Handle of the browse dialog.
// StatusText:
// The text message.
// Sets the status area text to the text message. You must have the bfStatusText flag
// set to see the status text.
//
// procedure SetSelectionPIDL(const Hwnd: HWND; const ItemIDList: PItemIDList);
// Hwnd:
// Handle of the browse dialog.
// ItemIDList:
// Pointer to an item identifier list, also know as a 'pidl', which identifies a folder.
// Sets the selection in the browse dialog to the folder represented by the pidl. The
// pidl is an opaque binary value and would need to be created by some other Shell API
// like SHGetSpecialFolderLocation. Don't forget to deallocate and pidl you obtain
// yourself with the CoTaskMemFree function or equivalent. Not generally as useful as
// the next method.
//
// procedure SetSelectionPath(const Hwnd: HWND; const Path: String);
// Hwnd:
// Handle of the browse dialog.
// Path:
// String value of the path to select.
// Sets the selection in the browse dialog to the folder in the Path parameter. The
// path can be in long or 8.3 format.
//
// procedure EnableOK(const Hwnd: HWND; const Value: Boolean);
// Hwnd:
// Handle of the browse dialog.
// Value:
// Desired state of the browse dialog OK button.
// Sets the enabled state of the OK button in the browse dialog. Note: You can use this
// to override the restrictions set in the Flags property, but if the user selects an item
// that the Flags item restricts, the returned directory will be an empty string.
//
//
// Events:
//
// OnInitialized(Hwnd: HWND; CallbackParam: LPARAM)
// Hwnd:
// Handle of the browse dialog.
// CallbackParam:
// Value of the CallbackParam property of the TBrowseFolder object whose Execute procedure
// was called.
// Fired when the browse dialog is done initializing.
//
// OnSelectionChanged(Hwnd: HWND; CallbackParam: LPARAM; const ItemIDList: PItemIDList)
// Hwnd:
// Handle of the browse dialog.
// CallbackParam:
// Value of the CallbackParam property of the TBrowseFolder object whose Execute procedure
// was called.
// ItemPidl:
// Pidl of the selected item. See the note below for info on getting the directory path
// from the pidl.
// Fired when a new folder in the browse dialog is selected.
//
//
// Comments:
//
// This component wraps a few of the Win32 shell functions to display the Windows standard
// folder browse dialog. Frankly, I hacked this component together over the course of a
// couple of evenings based on Microsoft's sketchy documentation and some of their C header
// files, so I can't vouch for the complete accuracy of the component. It does seem to work
// quite well, though, and I haven't experienced any problems with it, so I tend to think
// everything works smoothly. I also added to the basic functionality of showing the browse
// dialog the capability to show only particular directories, based on the handy
// SHGetSpecialFolderLocation function. Some of the folder locations might not be defined
// on your system, so you may want to only use the common folders like foDesktop or foNetwork
// in your software. If anyone has some comments on how I implemented the component
// (or simply knows better), please email me and correct any errors I've made. Please forgive
// me for documenting the component in the source file instead of generating a help file;
// I've tried to make up for it by extensively commenting the code.
//
//
// Hints:
//
// - If you want to get the selected directory path in the OnSelectionChanged event, use the
// SHGetPathFromIDList function on the ItemPidl parameter passed into the event handler.
// - The SetStatusText, SetSelectionPIDL, SetSelectionPath, and EnableOK methods ecapsulate
// the messages you can send to the browse dialog while it is active. Use these functions
// from within the TBrowseFolder event handlers to make changes to the browse dialog instead
// of using SendMessage (although that would be perfectly acceptable, and this file defines
// all the constants you would need.)
// - Use the SHGetFileInfo function to retrieve extended information about the selected folder.
// - For more information, lookup SHBrowseForFolder, BROWSEINFO, and BrowseCallbackProc in the
// Win32 online help.
// - Beware! The Microsoft documentation on these functions shipped with Delphi is not entirely
// accurate. In most cases, they've reversed the location of certain parameters sent to the
// callback function or of messages you can send to the browse dialog. Compare my implementation
// below with the documentation for more information.
//
//--------------------------------------------------------------------------------------------------------//
unit BrowseFolder;
interface
uses
Windows, Messages, Classes, Forms, Dialogs, SysUtils, Ole2, Shlobj;
type
{Browser notification events}
TBrowserInitializedEvent=procedure(Hwnd: HWND; CallbackParam: LPARAM) of object;
TSelectionChangedEvent=procedure(Hwnd: HWND; CallbackParam: LPARAM; const ItemIDList: PItemIDList) of object;
TBrowseInfoFlags=(bfFileSysDirsOnly,bfDontGoBelowDomain,bfStatusText,bfFileSysAncestors,bfBrowseForComputer,bfBrowseForPrinter);
TBrowseInfoFlagSet=set of TBrowseInfoFlags;
TSHFolders=(foDesktop,foPrograms,foControls,foPrinters,foPersonal,foFavorites,foStartup,foRecent,
foSendto,foRecycleBin,foStartMenu,foDesktopDirectory,foMyComputer,foNetwork,foNetworkNeighborhood,
foFonts,foTemplates);
const
NUMBER_OF_BROWSE_INFO_FLAGS=6;
BROWSE_FLAG_ARRAY: array[TBrowseInfoFlags] of Integer=
(BIF_RETURNONLYFSDIRS,BIF_DONTGOBELOWDOMAIN,BIF_STATUSTEXT,BIF_RETURNFSANCESTORS,
BIF_BROWSEFORCOMPUTER,BIF_BROWSEFORPRINTER);
SH_FOLDERS_ARRAY: array[TSHFolders] of Integer=
(CSIDL_DESKTOP,CSIDL_PROGRAMS,CSIDL_CONTROLS,CSIDL_PRINTERS,CSIDL_PERSONAL,CSIDL_FAVORITES,
CSIDL_STARTUP,CSIDL_RECENT,CSIDL_SENDTO,CSIDL_BITBUCKET,CSIDL_STARTMENU,CSIDL_DESKTOPDIRECTORY,
CSIDL_DRIVES,CSIDL_NETWORK,CSIDL_NETHOOD,CSIDL_FONTS,CSIDL_TEMPLATES);
type
EBrowseDialogAlreadyShowing=class(Exception);
{TBrowseFolder}
TBrowseFolder = class(TComponent)
private
FBrowseDialogShowing: Boolean;
FTitle: String;
FCallbackParam: LPARAM;
FDisplayName: String;
FImageIndex: Integer;
FDirectory: String;
FFlags: TBrowseInfoFlagSet;
FShowPathInStatusArea: Boolean;
FFolder: TSHFolders;
FOnInitialized: TBrowserInitializedEvent;
FOnSelectionChanged: TSelectionChangedEvent;
protected
public
constructor Create(AOwner: TComponent); override;
function Execute: Boolean;
procedure SetStatusText(const Hwnd: HWND; const StatusText: String);
procedure SetSelectionPIDL(const Hwnd: HWND; const ItemIDList: PItemIDList);
procedure SetSelectionPath(const Hwnd: HWND; const Path: String);
procedure EnableOK(const Hwnd: HWND; const Value: Boolean);
property DisplayName: String read FDisplayName;
property ImageIndex: Integer read FImageIndex;
property Directory: String read FDirectory;
property BrowseDialogShowing: Boolean read FBrowseDialogShowing;
published
property Title: String read FTitle write FTitle;
property CallbackParam: LPARAM read FCallbackParam write FCallbackParam;
property Flags: TBrowseInfoFlagSet read FFlags write FFlags;
property ShowFullPath: Boolean read FShowPathInStatusArea write FShowPathInStatusArea;
property Folder: TSHFolders read FFolder write FFolder default foDesktop;
property OnInitialized: TBrowserInitializedEvent read FOnInitialized write FOnInitialized;
property OnSelectionChanged: TSelectionChangedEvent read FOnSelectionChanged write FOnSelectionChanged;
end;
{Utility functions}
function CompressString(const Path, Separator, Replacement: String; MaxLength: Integer): String;
function BreakApart(const theString, Separator: String; var Tokens: TStringList): Integer;
{Callback procedure; must be declared with stdcall since Windows will be calling it}
procedure BrowserCallbackProc(hwnd: HWND; uMsg: Integer; lParam: LPARAM; lpData: LPARAM); stdcall;
{Note: The following function is alluded to in the Shlobj.pas file, but no record of it exists.
Instead, I use the CoTaskMemFree call to free any pidl's. From what I can tell, this should be
an equivalent call, since the Windows docs say that some of the task allocator API's are just
quick calls to the OLE2 functions. This should be one of them.}
//function SHFree(ItemIDList: PItemIDList): HRESULT; external 'SHELL32.dll' name 'SHFree';
procedure Register;
implementation
var
{Global method pointers needed because callback function does not work properly when it's a member of an object.
This is an unorthodox implementation requiring some kludgy global variables, but I couldn't get Windows to
call back a method pointer properly because there's no way to declare a method pointer as stdcall. If
anyone can come up with a solution, please mail me an updated copy.}
GlobalOnInitialized: TBrowserInitializedEvent;
GlobalOnSelectionChanged: TSelectionChangedEvent;
GlobalShowPathInStatusArea: Boolean;
GlobalBrowseDialogShowing: Boolean;
//--------------------------------------------------------------------------------------------------------//
procedure Register;
begin
RegisterComponents('Win95', [TBrowseFolder]);
end;
//--------------------------------------------------------------------------------------------------------//
{Compresses a string by replacing one or more components with the replacement string}
function CompressString(const Path, Separator, Replacement: String; MaxLength: Integer): String;
var
Tokens: TStringList;
function BuildPath(const Components: TStringList): String;
var
i: Integer;
begin
for i:=0 to Components.Count-1 do
if i=0 then
Result:=Components[i]
else
Result:=Result+Separator+Components[i];
end;
begin
try
Tokens:=TStringList.Create;
{Check if full path is less than MaxLength}
Result:=Path;
if StrLen(PChar(Result))<=MaxLength then
Exit;
{Check if can replace the 2nd token with the replacement and make length less than MaxLength}
if BreakApart(Result,Separator,Tokens)<3 then
Exit
else
begin
Tokens[1]:=Replacement;
Result:=BuildPath(Tokens);
end;
{Must continue to delete components until can get the length below the maximum}
while (StrLen(PChar(Result))>MaxLength) and (Tokens.Count>3) do
begin
Tokens.Delete(2);
Result:=BuildPath(Tokens);
end;
finally
Tokens.Free;
end;
end;
//--------------------------------------------------------------------------------------------------------//
{Breaks a string into tokens and places the tokens in a string list}
function BreakApart(const theString, Separator: String; var Tokens: TStringList): Integer;
var
Index: Integer;
CurrentString: String;
CurrentToken: String;
Done: Boolean;
begin
Result:=0;
CurrentString:=theString;
Done:=FALSE;
Tokens.Clear;
repeat
{Find the first separator in the string}
Index:=Pos(Separator,CurrentString);
{If separator not found, we are done}
if Index=0 then
begin
{Last token is whatever string is left}
CurrentToken:=CurrentString;
Done:=TRUE;
end
else
begin
{Get token and chop off the beginning}
CurrentToken:=Copy(CurrentString,1,Index-1);
CurrentString:=Copy(CurrentString,Index+1,Length(CurrentString)-Index);
end;
{Add the token to the string list}
Tokens.Add(CurrentToken);
Inc(Result);
until Done;
end;
//--------------------------------------------------------------------------------------------------------//
{Callback procedure; Windows calls this procedure upon certain events in the browse dialog. This fucntion
calls any defined event handlers for the current global event pointers.}
procedure BrowserCallbackProc(hwnd: HWND; uMsg: Integer; lParam: LPARAM; lpData: LPARAM);
var
Path: String;
begin
case uMsg of
BFFM_INITIALIZED:
if Assigned(GlobalOnInitialized) then
GlobalOnInitialized(hwnd,lParam);
BFFM_SELCHANGED:
begin
if Assigned(GlobalOnSelectionChanged) then
GlobalOnSelectionChanged(hwnd,lpData,PItemIDList(lParam));
if GlobalShowPathInStatusArea then
begin
SetLength(Path,MAX_PATH);
SHGetPathFromIDList(PItemIDList(lParam),PChar(Path));
Path:=CompressString(Path,'\','...',35);
SendMessage(hwnd,BFFM_SETSTATUSTEXT,0,Longint(PChar(Path)));
end;
end;
end;
end;
{TBrowseFolder}
//--------------------------------------------------------------------------------------------------------//
constructor TBrowseFolder.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
SetLength(FDisplayName,MAX_PATH);
SetLength(FDirectory,MAX_PATH);
end;
//--------------------------------------------------------------------------------------------------------//
{Use this function to set the status text of the browse dialog from within one of the TBrowseFolder event handlers}
procedure TBrowseFolder.SetStatusText(const Hwnd: HWND; const StatusText: String);
begin
SendMessage(Hwnd,BFFM_SETSTATUSTEXT,0,Longint(PChar(StatusText)));
end;
//--------------------------------------------------------------------------------------------------------//
{Use this function to set the selection of the browse dialog manually from within one of the TBrowseFolder event handlers}
procedure TBrowseFolder.SetSelectionPIDL(const Hwnd: HWND; const ItemIDList: PItemIDList);
begin
SendMessage(Hwnd,BFFM_SETSELECTION,Ord(FALSE),Longint(ItemIDList));
end;
//--------------------------------------------------------------------------------------------------------//
{Use this function to set the selection of the browse dialog manually from within one of the TBrowseFolder event handlers}
procedure TBrowseFolder.SetSelectionPath(const Hwnd: HWND; const Path: String);
begin
SendMessage(Hwnd,BFFM_SETSELECTION,Ord(TRUE),Longint(PChar(Path)));
end;
//--------------------------------------------------------------------------------------------------------//
{Use this function to enable/disable the OK button of the browse dialog from within one of the TBrowseFolder event handlers}
procedure TBrowseFolder.EnableOK(const Hwnd: HWND; const Value: Boolean);
begin
SendMessage(Hwnd,BFFM_ENABLEOK,0,Ord(Value));
end;
//--------------------------------------------------------------------------------------------------------//
{Use this function to show the browse dialog. While a browse dialog is showing, the program cannot show
another. If for some reason you try to show another dialog while one is already showing, this function
returns an EBrowseDialogAlreadyShowing exception. This shouldn't be a problem unless you try to show the
dialog from several independant threads}
function TBrowseFolder.Execute: Boolean;
var
BrowseInfo: TBrowseInfo;
ItemIDList: PItemIDList; //Pointer to a file ID list
TempOnInitialized: TBrowserInitializedEvent;
TempOnSelectionChanged: TSelectionChangedEvent;
TempShowPathInStatusArea: Boolean;
i: Integer;
TempPath: array[0..MAX_PATH] of Char; //To avoid some odd problems I've encountered casting strings as PChar's
begin
{Check for an already-showing dialog}
if FBrowseDialogShowing then
raise EBrowseDialogAlreadyShowing.Create('The browse dialog is already showing.');
try
{Block similar calls to this function while a browse dialog is already displayed}
GlobalBrowseDialogShowing:=TRUE;
{Save the global callback method pointers (which are shared by all instances of TBrowseFolder}
TempOnInitialized:=GlobalOnInitialized;
TempOnSelectionChanged:=GlobalOnSelectionChanged;
TempShowPathInStatusArea:=GlobalShowPathInStatusArea;
{Init the BrowseInfo structure}
BrowseInfo.hwndOwner:=Application.Handle;
{Get the pointer to the appropriate folder pidl}
SHGetSpecialFolderLocation(Application.Handle,SH_FOLDERS_ARRAY[FFolder],BrowseInfo.pidlRoot);
BrowseInfo.pszDisplayName:=PChar(FDisplayName);
BrowseInfo.lpszTitle:=PChar(FTitle);
{OR all the flags together}
BrowseInfo.ulFlags:=0;
for i:=0 to NUMBER_OF_BROWSE_INFO_FLAGS-1 do
if TBrowseInfoFlags(i) in FFlags then
BrowseInfo.ulFlags:=BrowseInfo.ulFlags or BROWSE_FLAG_ARRAY[TBrowseInfoFlags(i)];
{Change the global pointers to point to this object's handlers so the non-member callback function can access them}
GlobalOnInitialized:=FOnInitialized;
GlobalOnSelectionChanged:=FOnSelectionChanged;
GlobalShowPathInStatusArea:=FShowPathInStatusArea;
BrowseInfo.lpfn:=@BrowserCallbackProc;
BrowseInfo.lParam:=FCallbackParam;
BrowseInfo.iImage:=0;
{Show the dialog}
FBrowseDialogShowing:=TRUE;
ItemIDList:=SHBrowseForFolder(BrowseInfo);
Result:=ItemIDList<>nil;
if Result then
begin
//SHGetPathFromIDList(ItemIDList,PChar(FDirectory));
SHGetPathFromIDList(ItemIDList,TempPath);
FDirectory:=StrPas(TempPath);
FImageIndex:=BrowseInfo.iImage;
end;
finally
{Free the ID lists with the system task allocator}
CoTaskMemFree(ItemIDList);
CoTaskMemFree(BrowseInfo.pidlRoot);
FBrowseDialogShowing:=FALSE;
{Restore the global pointers}
GlobalOnInitialized:=TempOnInitialized;
GlobalOnSelectionChanged:=TempOnSelectionChanged;
GlobalShowPathInStatusArea:=TempShowPathInStatusArea;
end;
end;
end.